package cn.example;

import org.redisson.Redisson;
import org.redisson.RedissonRedLock;
import org.redisson.api.RLock;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * Created by lanxw
 */
@RestController
public class SeckillController {
    @Autowired
    private Redisson redisson;
    @Autowired
    private Redisson redisson1;
    @Autowired
    private Redisson redisson2;
    @Autowired
    private Redisson redisson3;
    @Autowired
    private StringRedisTemplate redisTemplate;

    @RequestMapping("/init")
    public String init() {
        redisTemplate.opsForValue().set("stock", "50");
        return "初始化成功";
    }

    /**
     * 优化原因:为了在多线程环境下实现线程安全,因此在想办法让代码变为原子性,首先想到的就是加上同步锁synchronized
     * synchronized的作用：
     * 就是,让多线程按照顺序依次访问synchronized包裹的代码.
     * 就是让synchronized包裹的代码同一时间只有一个线程在操作，变相的将这些代码变成原子性的了.
     * 优化版本1
     */
    @RequestMapping("/deduct_stock/v1")
    public String deductStockVersionOne() {
        synchronized (this) {
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        }
        return "end";
    }

    /**
     * 优化版本2
     */
    @RequestMapping("/deduct_stock/v2")
    public String deductStockVersionTwo() {
        String lockKey = "product_lock:1";
        try {
            //相当于setNx的命令
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey,
                    "wolfcode");
            if (!lock) {
                return "error";
            }
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            //释放锁
            redisTemplate.delete(lockKey);
        }
        return "end";
    }

    /**
     * 优化版本3
     */
    @RequestMapping("/deduct_stock/v3")
    public String deductStockVersionThree() {
        String lockKey = "product_lock:1";
        try {
            //相当于setNx的命令
            Boolean lock = redisTemplate.opsForValue().setIfAbsent(lockKey,
                    "wolfcode");
            redisTemplate.expire(lockKey, 10, TimeUnit.SECONDS);
            if (!lock) {
                return "error";
            }
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            //释放锁
            redisTemplate.delete(lockKey);
        }
        return "end";
    }

    /**
     * 优化原因:
     * 之前设置了分布式锁的失效时间为10s,而如果出现业务需要15s的,就会提前释放,此时业务未完成
     * 优化版本4(自己加的锁只能自己来解)
     */
    @RequestMapping("/deduct_stock/v4")
    public String deductStockVersionFour() {
        String lockKey = "product_lock:1";
        String clientId = UUID.randomUUID().toString();
        try {
            //相当于setNx的命令
            //value存储的是这个线程生成出来唯一的ID
            Boolean lock = redisTemplate.opsForValue()
                    .setIfAbsent(lockKey, clientId, 10, TimeUnit.SECONDS);
            if (!lock) {
                return "error";
            }
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            //释放锁
            //在释放锁之前，判断锁的value释放和当前线程client一致，如果一致说明是自己的锁，可以释放
            if (clientId.equals(redisTemplate.opsForValue().get(lockKey))) {
                redisTemplate.delete(lockKey);
            }
        }
        return "end";
    }

    /**
     * 优化版本5
     */
    @RequestMapping("/deduct_stock/v5")
    public String deductStockVersionFive() {
        String lockKey = "product_lock:1";
        RLock lock = redisson.getLock(lockKey);
        try {
            //加锁,如果获得锁,继续执行后续代码,如果没有获得锁,阻塞
            lock.lock(10, TimeUnit.SECONDS);
            //tryLock尝试获得锁,如果获取到,返回true,获取不到,返回false
//            boolean b = lock.tryLock();
            //可以进行有限时间的阻塞等待,如果获得锁返回true,如果获取不到,返回false
//            boolean b = lock.tryLock(5, 10, TimeUnit.SECONDS);
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            //释放锁   lua脚本语言,保证多个操作之间的原子性
            lock.unlock();
        }
        return "end";
    }

    /**
     * 优化版本6
     */
    @RequestMapping("/deduct_stock/v6")
    public String deductStockVersionSix() {
        String lockKey = "product_lock:1";
        RLock lock1 = redisson1.getLock(lockKey);//Redis服务器1
        RLock lock2 = redisson2.getLock(lockKey);//Redis服务器2
        RLock lock3 = redisson3.getLock(lockKey);//Redis服务器3
        RedissonRedLock redLock = new RedissonRedLock(lock1, lock2, lock3);
        try {
            //加锁

            redLock.lock();
            int stock = Integer.parseInt(redisTemplate.opsForValue().get("stock"));
            if (stock > 0) {
                int realStock = stock - 1;
                redisTemplate.opsForValue().set("stock", String.valueOf(realStock));
                System.out.println("扣减成功,剩余库存:" + realStock);
            } else {
                System.out.println("扣减失败，库存不足");
            }
        } finally {
            //释放锁
            redLock.unlock();
        }
        return "end";

    }


}
